/**
 * Copyright 2009 Jesse Brown (jbrown@fluentv.com)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *   http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package com.fluentv.games.netblocks.piece;

import java.io.Serializable;
import java.util.Arrays;

/**
 * Abstract Shape. 
 *
 * @author Jesse Brown (jbrown@fluentv.com)
 */
public class Shape implements Serializable {      
  private static final long serialVersionUID = 1L;

  protected final int color;
  
  /** 
   * Shape cell mask, where 0,0 is the TOP LEFT of the shape
   * The first dimension is height, the second dimension is width. 
   */ 
  protected boolean[][] shapeMask = new boolean[0][0];
  
  /**
   * Default constructor. Use a manually specified shape mask, and 
   * color.
   */
  public Shape(int newColor, boolean[][] newShapeMask) {
    color = newColor;
    shapeMask = newShapeMask;
  }
         
  /**
   * Retrieve the current shape mask.
   */
  public boolean[][] getMask() {
    return shapeMask;
  }
  
  /**
   * Retrieve the largest width (in units) of the shape.
   */
  public int getWidth() {
    return shapeMask[0].length;
  }
  
  /**
   * Retrieve the largest height (in units) of the shape.
   */
  public int getHeight() {
    return shapeMask.length;
  }
  
  /**
   * Shape color
   * @return color
   */
  public int getColor() {
    return color;
  } 
  
  /**
   * Iterate over each cell of a mask, calling the specified MaskCellProcessor
   * on each cell.
   * 
   * @param mask boolean[][] mask
   * @param processor MaskCellProcessor to call on each cell value
   * @return True if processing completed, false if processing aborted due to 
   *         a MaskCellProcessor call returning false
   */
  public boolean processMask(MaskCellProcessor processor) {
    for (int y = 0; y < shapeMask.length; y++) {
      for (int x = 0; x < shapeMask[y].length; x++) {
        if (!processor.processMaskCell(shapeMask[y][x], x, y)) {
          return false;
        }
      }
    }
    return true;
  }
  
  /**
   * Rotate the shape, returning a new, rotated shape.  
   */
  public Shape rotate() {    
    // New array, reversing the order.
    boolean[][] newMask = new boolean[getWidth()][getHeight()];    
    RotateCellProcessor rotateProcessor = 
        new RotateCellProcessor(newMask, getHeight());
    
    processMask(rotateProcessor);

    return new Shape(getColor(), rotateProcessor.getOutputArray());       
  }
  
  /**
   * Interface for classes that process mask cells from left to right and top to bottom.
   */
  public interface MaskCellProcessor {
    /**
     * Process a mask from the 'top left' most cell, calling processMaskCell on each cell
     * within the mask. 
     * 
     * @param cellValue value of the mask cell 
     * @param xOffset offset from the left (i.e. 0 if the leftmost cell)
     * @param yOffset offset from the top 
     * @return true, continue processing. false, abort processing. 
     */
    boolean processMaskCell(boolean cellValue, int xOffset, int yOffset);
  }
  
  /**
   * Rotate all cells using the MaskCellProcessor
   */
  private class RotateCellProcessor implements MaskCellProcessor {
    private boolean[][] outputArray;
    private int newWidth;
    
    RotateCellProcessor(boolean[][] newArray, int newWidth) {
      outputArray = newArray;      
      this.newWidth = newWidth;
    }
    
    public boolean[][] getOutputArray() {
      return outputArray;
    }
    
    @Override
    public boolean processMaskCell(boolean cellValue, int xOffset, int yOffset) {
      
      outputArray[xOffset][newWidth - 1 - yOffset] = cellValue;
      return true;
    }
  }
  
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + color;
    result = prime * result + Arrays.hashCode(shapeMask);
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (!(obj instanceof Shape))
      return false;
    Shape other = (Shape) obj;
    if (color != other.color)
      return false;
    if (!Arrays.equals(shapeMask, other.shapeMask))
      return false;
    return true;
  }
}
